//
// Created by antler on 2018/12/7.
//

#include <string>

#include "lua.hpp"
#include "luaext.hpp"

#include "log.h"
#include "GlobalMacros.h"

#include "Size.h"

static int size_constructor(lua_State *L) {
    Size *ret = nullptr;

    int argc = lua_gettop(L) - 1;
    if (argc == 0) {
        ret = Size::create();
    } else if (argc == 1) {
        tolua_Error tolua_err;
        if (tolua_isusertype(L, 2, USERTYPE_POINT, 0, &tolua_err)) {
            Point *obj = (Point *) tolua_tousertype(L, 2, nullptr);
            if (obj) {
                ret = Size::createWith(*obj);
            }
        } else if (tolua_isusertype(L, 2, USERTYPE_SIZE, 0, &tolua_err)) {
            Size *obj = (Size *) tolua_tousertype(L, 2, nullptr);
            if (obj) {
                ret = Size::createWith(*obj);
            }
        }
    } else if (argc == 2) {
        int width = luaL_checkint(L, 2);
        int height = luaL_checkint(L, 3);
        ret = Size::createWith(width, height);
    }

    if (ret) {
        LOGV("size:constructor self:%p", ret);
        tolua_pushusertype_and_takeownership(L, ret, USERTYPE_SIZE);
    } else {
        lua_pushnil(L);
    }
    return 1;
}

static int size_destructor(lua_State *L) {
    Size *self = (Size *) tolua_tousertype(L, 1, 0);
    LOGV("Size:destructor self:%p", self);
    SAFE_DELETE(self);
    return 0;
}

int size_tostring(lua_State *L) {
    Size *self = (Size *) toluahelper_tousertype(L, 1, USERTYPE_SIZE, nullptr);
    if (self) {
        lua_pushfstring(L, "%s[%d x %d]", tolua_typename(L, 1), self->width, self->height);
    } else {
        lua_pushfstring(L, "class: %p", lua_topointer(L, 1));
    }
    return 1;
}

static int size_add(lua_State *L) {
    Size *obj1 = (Size *) toluahelper_tousertype(L, 1, USERTYPE_SIZE, nullptr);
    Size *obj2 = (Size *) toluahelper_tousertype(L, 2, USERTYPE_SIZE, nullptr);
    if (!obj1 || !obj2) {
        tolua_error(L, "[no object] in operator 'add'", nullptr);
    }

    Size *newObj = Size::createWith(*obj1 + *obj2);
    tolua_pushusertype_and_takeownership(L, newObj, USERTYPE_SIZE);
    return 1;
}

static int size_sub(lua_State *L) {
    Size *obj1 = (Size *) toluahelper_tousertype(L, 1, USERTYPE_SIZE, nullptr);
    Size *obj2 = (Size *) toluahelper_tousertype(L, 2, USERTYPE_SIZE, nullptr);
    if (!obj1 || !obj2) {
        tolua_error(L, "[no object] in operator 'sub'", nullptr);
    }

    Size *newObj = Size::createWith(*obj1 - *obj2);
    tolua_pushusertype_and_takeownership(L, newObj, USERTYPE_SIZE);
    return 1;
}

static int size_mul(lua_State *L) {
    int type1 = lua_type(L, 1);
    int type2 = lua_type(L, 2);

    Size *obj = nullptr;
    int ratio = 0;

    if (type1 == LUA_TUSERDATA && type2 == LUA_TNUMBER) {
        obj = (Size *) toluahelper_tousertype(L, 1, USERTYPE_SIZE, nullptr);
        ratio = luaL_checkint(L, 2);
    } else if (type1 == LUA_TNUMBER && type2 == LUA_TUSERDATA) {
        obj = (Size *) toluahelper_tousertype(L, 2, USERTYPE_SIZE, nullptr);
        ratio = luaL_checkint(L, 1);
    }

    if (!obj) {
        tolua_error(L, "[no object] in operator 'mul'", nullptr);
    }

    Size *newObj = Size::createWith(*obj * ratio);
    tolua_pushusertype_and_takeownership(L, newObj, USERTYPE_SIZE);
    return 1;
}

static int size_div(lua_State *L) {
    Size *obj = (Size *) toluahelper_tousertype(L, 1, USERTYPE_SIZE, nullptr);
    int ratio = luaL_checkint(L, 2);

    if (!obj) {
        tolua_error(L, "[no object] in operator 'div'", nullptr);
    }

    if (ratio == 0) {
        tolua_error(L, "dividend is zero in operator 'div'", nullptr);
    }

    Size *newObj = Size::createWith(*obj / ratio);
    tolua_pushusertype_and_takeownership(L, newObj, USERTYPE_SIZE);
    return 1;
}

static int size_eq(lua_State *L) {
    Size *obj1 = (Size *) toluahelper_tousertype(L, 1, USERTYPE_SIZE, nullptr);
    Size *obj2 = (Size *) toluahelper_tousertype(L, 2, USERTYPE_SIZE, nullptr);
    if (!obj1 || !obj2) {
        tolua_error(L, "[no object] in operator 'eq'", nullptr);
    }

    lua_pushboolean(L, *obj1 == *obj2 ? 1 : 0);
    return 1;
}

static int size_unm(lua_State *L) {
    Size *obj = (Size *) toluahelper_tousertype(L, 1, USERTYPE_SIZE, nullptr);
    if (!obj) {
        tolua_error(L, "[no object] in operator '-'", nullptr);
    }

    Size *newObj = Size::createWith(-(*obj));
    tolua_pushusertype_and_takeownership(L, newObj, USERTYPE_SIZE);
    return 1;
}

static const luaL_Reg funcs[] = {
        {".call",      size_constructor},
        {"__tostring", size_tostring},
        {"__add",      size_add},
        {"__sub",      size_sub},
        {"__mul",      size_mul},
        {"__div",      size_div},
        {"__eq",       size_eq},
        {"__unm",      size_unm},
        {nullptr,      nullptr}
};

static int get_ZERO(lua_State *L) {
    tolua_pushusertype(L, &Size::ZERO, USERTYPE_SIZE);
    return 1;
}

static int get_INVALID(lua_State *L) {
    tolua_pushusertype(L, &Size::INVALID, USERTYPE_SIZE);
    return 1;
}

static int get_width(lua_State *L) {
    Size *self = (Size *) tolua_tousertype(L, 1, nullptr);
    if (!self) {
        tolua_error(L, "[no object] in filed 'width'", nullptr);
    }
    lua_pushinteger(L, self->width);
    return 1;
}

static int set_width(lua_State *L) {
    Size *self = (Size *) tolua_tousertype(L, 1, nullptr);
    if (!self) {
        tolua_error(L, "[no object] in filed 'width'", nullptr);
    }
    self->width = luaL_checkinteger(L, 2);
    return 0;
}

static int get_height(lua_State *L) {
    Size *self = (Size *) tolua_tousertype(L, 1, nullptr);
    if (!self) {
        tolua_error(L, "[no object] in filed 'height'", nullptr);
    }
    lua_pushinteger(L, self->height);
    return 1;
}

static int set_height(lua_State *L) {
    Size *self = (Size *) tolua_tousertype(L, 1, nullptr);
    if (!self) {
        tolua_error(L, "[no object] in filed 'height'", nullptr);
    }
    self->height = luaL_checkinteger(L, 2);
    return 0;
}

LUAMOD_API int luaopen_size(lua_State *L) {
    tolua_open(L);

    tolua_module(L, nullptr, 0);
    tolua_beginmodule(L, nullptr);
    {
        tolua_usertype(L, USERTYPE_SIZE);
        tolua_cclass(L, CLASSNAME_SIZE, USERTYPE_SIZE, "", size_destructor);

        tolua_beginmodule(L, CLASSNAME_SIZE);
        {
            toluahelper_setfunc(L, funcs);

            TOLUAHELPER_CLASS_CONST_VARIABLE(L, ZERO);
            TOLUAHELPER_CLASS_CONST_VARIABLE(L, INVALID);

            TOLUAHELPER_CLASS_VARIABLE(L, width);
            TOLUAHELPER_CLASS_VARIABLE(L, height);
        }
        tolua_endmodule(L);
    }
    tolua_endmodule(L);
    return 0;
}